#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <common/macro/build.h>
#include <common/arity.h>
#include <common/datatype.h>
#include <common/predicate.h>
#include <common/util.h>

/*
 * Manifest format:
 * 2B number for number of arguments
 * ASCIIZ datatype name
 * Actual data
 */

struct call_args_t {
	char *predicate;
	char **arguments;
	size_t *argsizes;
	int argc;
};

API_FILELOCAL void *call_compile(void *datatype_args, size_t *argsizes, char **arguments, int argc) {
	int i;
	struct call_args_t *call_args;

	call_args = malloc(sizeof(*call_args));
	call_args->argc = argc - 1;
	call_args->predicate = strdup(arguments[0]);

	call_args->arguments = malloc(sizeof(*call_args->arguments) * call_args->argc);
	for (i = 0; i < call_args->argc; i++) {
		call_args->arguments[i] = strdup(arguments[i + 1]);
	}
	
	call_args->argsizes = util_memdup(argsizes, sizeof(*call_args->argsizes) * call_args->argc);

	return call_args;
}

API_FILELOCAL void *call_execute(char *data, size_t size, void *predicate_arg, void **internal) {
	int rv;
	datatype_t *datatype;
	predicate_t *predicate;
	struct call_args_t *call_args;
	void *dt_args, *pred_args, *pred_ctx;

	char **payload_dt_args, *payload_data;
	size_t payload_size, i;
	uint16_t payload_dt_args_c;

	call_args = (struct call_args_t *)predicate_arg;

	/* 1. First, look up the datatype. If it doesn't exist, fail this item. */
	datatype = datatype_lookup(&data[2]);
	if (datatype == NULL)
		return 0;
	
	/* 2. Now, check for the predicate. Again, if not found, fail. */
	predicate = datatype_predicate_lookup(datatype, call_args->predicate);
	if (predicate == NULL)
		return 0;

	/*   ...also do an arity check. */
	if (!arity_check(predicate->minargs, predicate->maxargs, call_args->argc))
		return 0;
	
	/* 3. Compile the arguments for the datatype. If not possible, fail. */

	/*   ...at this stage, we also have to parse the actual data. */
	payload_data = data;
	payload_dt_args_c = *(uint16_t*)payload_data;

	payload_size = size - sizeof(uint16_t);
	payload_data = &data[size - payload_size];

	/*  - skip datatype. */
	payload_size -= strlen(payload_data) + 1;
	payload_data = &data[size - payload_size];

	/*  - store the arguments. */
	payload_dt_args = malloc(sizeof(*payload_dt_args) * payload_dt_args_c);
	for (i = 0; i < payload_dt_args_c; i++) {
		payload_dt_args[i] = payload_data;
		payload_size -= strlen(payload_data) + 1;
		payload_data = &data[size - payload_size];	
	}

	/*  - and now, payload_data points to the start of the actual data. */

	dt_args = datatype->args_parse(payload_dt_args, payload_dt_args_c); /* TODO: retrieve arguments for datatype. */
	if (dt_args == NULL) {
		free(payload_dt_args);
		return 0;
	}

	/* 4. Compile the arguments for the predicate. If not possible, fail. */
	pred_args = predicate->compile(dt_args, call_args->argsizes, call_args->arguments, call_args->argc);
	if (pred_args == NULL) {
		free(payload_dt_args);
		datatype->args_destroy(dt_args);
		return 0;
	}

	/* 5. Execute the given predicate. Store the result. */
	pred_ctx = NULL;
	rv = predicate->execute(payload_data, payload_size, pred_args, &pred_ctx);
	free(pred_ctx);

	/* 6. Release the arguments for the predicate. */
	predicate->destroy(pred_args);

	/* 7. Release the arguments for the datatype. */
	datatype->args_destroy(dt_args);
	free(payload_dt_args);

	return rv;
}

API_FILELOCAL void *call_release(void *predicate_arg) {
	int i;
	struct call_args_t *call_args;

	call_args = (struct call_args_t *)predicate_arg;

	for (i = 0; i < call_args->argc; i++) {
		free(call_args->arguments[i]);
	}
	free(call_args->predicate);
	free(predicate_arg);
}


API_FILELOCAL void *datatype_match_compile(void *datatype_args, size_t *argsizes, char **arguments, int argc) {
	return strdup(arguments[0]);
}

API_FILELOCAL int datatype_match_execute(char *data, size_t size, void *predicate_arg, void **internal) {
	/* If the data is invalid, then it's an obvious failure. */
	if (size < 3)
		return 0;
	
	return (strcmp(&data[2], (char*)predicate_arg) == 0 ? 1 : 0);
}

API_FILELOCAL void datatype_match_release(void *predicate_arg) {
	free(predicate_arg);
}

API_FILELOCAL predicate_t predicates[] = {
	{"call", 1, -1, call_compile, call_execute, call_release},
	{"datatype", 1, 1, datatype_match_compile, datatype_match_execute, datatype_match_release},
};


API_LIBLOCAL datatype_t dt_manifest = {
	"manifest",

	0, 0,
	NULL, NULL,

	NULL,
	NULL, /* TODO */

	predicates,
};
